/*
	Copyright (c) 2008, Grant Jones <grant@podtronics.com>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or
	without modification, are permitted provided that the following
	conditions are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
		
		Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in
		the documentation and/or other materials provided with the
		distribution.
		
		Neither the name of the gxjones.com nor the names of its
		contributors may be used to endorse or promote products derived
		from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
	OF THE POSSIBILITY OF SUCH DAMAGE.
	
	http://www.opensource.org/licenses/bsd-license.php
*/

/*
 *  asb_request.c
 *  scatterd
 *
 *  Created by Grant Jones on 3/14/08.
 *
 */


#include "edb_main.h"
#include "edb_net_shell.h"

#include "asdb_request.h"
#include "asdb_response.h"

#include "edb_net_utils.h"

#include <openssl/hmac.h>
#include <openssl/bio.h>
#include <openssl/evp.h>



/*

http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/

1. Create the string you'll use when generating the signature:

Sort all request parameters alphabetically, ignoring case.

Include SignatureVersion in the list but not Signature. Do not list as "empty" any optional parameters that are not included in the request. In other words, if no value for ParameterA is specified in the request, do not include a ParameterA entry in this sorted list.

Form a string by concatenating each request parameter's name with its value.

The format of the string is:

Param-name1Param-value1Param-name2Param-value2...Param-nameNParam-valueN
The parameter names are case sensitive. Do not include any separators in this string, such as question marks (?), ampersands (&), or equals signs (=). Do not URL encode the parameter values.

2. Calculate an RFC 2104-compliant HMAC-SHA1 Signatures, using your Secret Access Key as the key and the string you just created.

For more information, go to http://www.faqs.org/rfcs/rfc2104.html.

3. Convert the resulting value to base64.

4. URL encode the resulting value as specified in RFC 1738, section 2.2.

This is required because base64 encoding can result in characters that are not legal in a URL, such as plus signs (+), slashes (/), and equals signs (=).

5. Pass this final value in the Signature request parameter.


*/
#ifdef DEBUG_PRINT
static void asdb_print_keys( CMAP_KEY_TYPE key, void *data, void *ctx )
{
	edb_value_t *v = data;
	
	edb_log_printf(0,"key %d: ", key);
	edb_log_dump(0, v->z, v->n );
	edb_log_printf(0,"\n");
}
#endif

static void asdb_concat_kv( CMAP_KEY_TYPE i, void *data, void *ctx )
{
	asdb_req_ctx *r = ctx;
	edb_value_t *key = data;
	edb_value_t *value = strmap_lookup(r->req_params, (const char *)key->z, key->n);
	
	assert(value != NULL);
	
	cmap_str_append_bytes(r->strout, (const char *)key->z, key->n);
	cmap_str_append_bytes(r->strout, (const char *)value->z, value->n);
}

static int asdb_req_sort(void *ctx, const void *a, const void *b)
{
	const edb_value_t *av = a;
	const edb_value_t *bv = b;
	
	int c;
//	c = memcmp(av->z, bv->z, av->n < bv->n ? av->n : bv->n );
#ifndef WIN32
	c = strncasecmp((const char *)av->z, (const char *)bv->z, av->n < bv->n ? av->n : bv->n );
#else
	c = StrCmpNI(av->z, bv->z, av->n < bv->n ? av->n : bv->n );
#endif
	if( c==0 )
		c = ((int)av->n) - ((int)bv->n);
		
	return c;
}


cmapStruct *asdb_req_sortparams( strmapStruct *req_params )
{
	cmapStruct *sorted_out = cmap_new( req_params->mempool );
	strmapEnumStruct e;
	char *key;
	size_t key_sz;
	if(!sorted_out)
		return NULL;
		
	/* pull all keys out of the strmap */
	strmap_enum( req_params, &e );
	while( strmap_enum_next( &e, &key, &key_sz ) != NULL )
	{
		edb_value_t *v = cmap_assign_new(sorted_out, cmap_gethighestkey(sorted_out)+1, sizeof(edb_value_t) );
		v->n = key_sz;
		v->z = (unsigned char *)key;
	}
	
	/* sort keys */
	cmap_sortbyvalue( sorted_out, NULL, asdb_req_sort );

	return sorted_out;
}


char *asdb_req_create_sig( mempool_t *pool, strmapStruct *access_info, strmapStruct *req_params )
{
	cmapStruct *sorted_keys = asdb_req_sortparams( req_params );
	asdb_req_ctx r;
	unsigned char md[EVP_MAX_MD_SIZE];	/* message digest */
	unsigned int md_len;
	
//	int x;
		
	r.req_params = req_params;
	r.strout = cmap_new(pool);

	/* create joined key/value pairs in alphabetical order: */
	cmap_iterate(sorted_keys, &r, asdb_concat_kv );

	size_t len_out = 0;
	
	/* note: len_out INCLUDES the null terminator in its count */
	char *sigstr = cmap_str_join(r.strout, &len_out);
/*	
	edb_log_printf(0,"sigstr (len:%ld) is:\n", (long)len_out);
	edb_log_dump(0, sigstr, len_out);
	edb_log_printf(0,"\n");
	edb_log_printf(0,"sigstr (len:%ld) is:\n", (long)len_out);
	for( x = 0; x < len_out-1; x++ )
		edb_log_printf(0,"%x ", sigstr[x] );	
	edb_log_printf(0,"\n");
*/

	edb_value_t *secret_key = strmap_lookup(access_info, ASDB_SECRET_KEY, ASDB_SECRET_KEY_SZ );
	if(!secret_key)
	{
		edb_log_printf(0,"no access key specified unable to sign request\n");
		return NULL;
	}



	/* digest data with secret access key via OpenSSL's convenient HMAC function,
	** note len_out-1 because cmap_str_join includes a NULL-term in its count. Tripped
	** me up while trying to validate the output using the Amazon SQS HTTP Query
	** Signature Tester */ 
	HMAC( EVP_sha1(), secret_key->z, secret_key->n, (const unsigned char *)sigstr, len_out-1, md, &md_len );
	
	
	cmapStruct *sigmap = edb_net_base64string( pool, (const char *)md, md_len );
	size_t b64_sig_len;
	char *b64_sig = cmap_str_join(sigmap, &b64_sig_len);
/*	
	edb_log_printf(0,"signature (len:%ld) is:\n", (long)md_len);

	for( x = 0; x < md_len; x++ )
		edb_log_printf(0,"%.2x ", md[x] );
	edb_log_printf(0,"\n");	

	edb_log_printf(0,"base64 signature (len:%ld) is:\n", (long)b64_sig_len);
	edb_log_dump(0, b64_sig, b64_sig_len );
	edb_log_printf(0,"\n");	
*/
//	char *encoded_b64_sig = edb_net_URIEncode( pool, b64_sig, b64_sig_len-1 );
//	edb_log_printf(0,"URI encoded base-64 signature: %s\n", encoded_b64_sig );
	
	return b64_sig;
}

/*!
	@function asdb_req_create
	@abstract combines req_params into a valid "REST" URL request, this
	          includes creating the request signature
	@discussion
	@param pool
	@param access_info
	@param req_params should not include the signature param
	@result a NULL terminated request
*/
char *asdb_req_create( mempool_t *pool, strmapStruct *access_info, strmapStruct *req_params )
{
	edb_value_t *access_key;
	char *sig_val;
	strmapEnumStruct e;
	char *key;
	size_t key_sz;
	int first_kv = 1;
	edb_value_t *val;
	size_t len_out;
	
	assert( access_info != NULL );
	assert( req_params != NULL );

	/* add the access key to the req_params */
	access_key = strmap_lookup(access_info, ASDB_ACCESS_KEY, ASDB_ACCESS_KEY_SZ );
	if(!access_key)
		return NULL;
	strmap_assign(req_params, "AWSAccessKeyId", strlen("AWSAccessKeyId"), access_key );

	/* calculate the signature based on the params thus far, the Signature is added after
	** the signature is created (obviously).
	*/
	sig_val = asdb_req_create_sig( pool, access_info, req_params );
	
	/* add the signature to the params list */
	strmap_assign(req_params, "Signature", strlen("Signature"), asdb_req_mkval(pool, sig_val, strlen(sig_val) ) );

	const char *simpledb_addr = "/?";
	cmapStruct *req_strparts = cmap_new(pool);
	
	cmap_str_append_bytes(req_strparts, simpledb_addr, strlen(simpledb_addr) );
	
	strmap_enum( req_params, &e );
	while( (val = strmap_enum_next( &e, &key, &key_sz )) )
	{	
		if(!first_kv)
			cmap_str_append_bytes(req_strparts, "&", 1 );
		char *uri_encoded_key = edb_net_URIEncode( pool, (const unsigned char *)key, key_sz );
		char *uri_encoded_value = edb_net_URIEncode( pool, val->z, val->n );
		
		cmap_str_append_bytes(req_strparts, uri_encoded_key, strlen(uri_encoded_key) );
		cmap_str_append_bytes(req_strparts, "=", 1 );
		cmap_str_append_bytes(req_strparts, uri_encoded_value, strlen(uri_encoded_value) );
		
		first_kv = 0;
	}
	

	return cmap_str_join(req_strparts, &len_out);
}

void asdb_req_defaults( mempool_t *pool, strmapStruct *req_params )
{
	/* using signature version 1 */
	strmap_assign( req_params, "SignatureVersion", 16, asdb_req_mkval( pool, "1", 1 ) );
	strmap_assign( req_params, "Version", 7, asdb_req_mkval( pool, "2007-11-07", 10 ) );
	strmap_assign( req_params, "Timestamp", 9, asdb_req_mktimestamp( pool ) );

}

edb_value_t *asdb_req_mktimestamp( mempool_t *pool )
{
	const int max_timestamp_sz = 40;
	char *timestamp_str = mempool_malloc_named(pool, max_timestamp_sz, __FUNCTION__);
	time_t clock = time(NULL);
	struct tm *t = gmtime(&clock);
	
	/* '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)? */
	snprintf(timestamp_str, max_timestamp_sz, "%d-%.2d-%.2dT%.2d:%.2d:%.2d", 
				1900+t->tm_year, 1+t->tm_mon, t->tm_mday,
				t->tm_hour, t->tm_min, t->tm_sec );
	return asdb_req_mkval( pool, timestamp_str, strlen(timestamp_str) );
}

edb_value_t *asdb_req_mkval( mempool_t *pool, const char *z, size_t n )
{
	edb_value_t *v = mempool_malloc_named(pool, sizeof(edb_value_t)+n, "asdb_req_mkval" );
	if(!v)
		return NULL;
	v->z = &((unsigned char *)v)[sizeof(edb_value_t)];
//	v->z = (const unsigned char *) z;
	v->n = n;
	memcpy( (unsigned char *)v->z, z, n );
	return v;
}

edb_value_t *asdb_req_mkval2( mempool_t *pool, const char *z )
{
	return asdb_req_mkval( pool, z, strlen(z) );
}


int asdb_set_attrs( strmapStruct *req_params, strmapStruct *item_attrs )
{
	strmapEnumStruct e;
	char *key;
	size_t key_sz;
	int attr_count = 0;
	char attr_name_buf[25];	/* 15 for "Attribute..Name" + 10 for a very large number and NULL term */
	edb_net_item_attr_t *attr;
	
	/* pull all keys out of the strmap */
	strmap_enum( item_attrs, &e );
	while( (attr = strmap_enum_next( &e, &key, &key_sz )) != NULL )
	{
		if( key_sz == 5 && memcmp( "_name", key, 5 ) == 0 )	/* skip _name attribute -- it's used as ItemName */
			continue;
	
		/* Attribute.X.Name */
		snprintf( attr_name_buf, 24, "Attribute.%d.Name", attr_count);
		strmap_assign( req_params, attr_name_buf, strlen(attr_name_buf),
						asdb_req_mkval( req_params->mempool, key, key_sz ) );

		/* Attribute.X.Value */
		snprintf( attr_name_buf, 24, "Attribute.%d.Value", attr_count);
		strmap_assign( req_params, attr_name_buf, strlen(attr_name_buf),
						asdb_req_mkval( req_params->mempool, attr->value, attr->value_sz ) );
				
	
		attr_count ++;
	}
	return attr_count;
}

/* store an item into a domain */
int asdb_queue_store( simpledb_t *sdb, const char *domain, size_t domain_sz, cmapStruct *affected_item )
{
	/* construct the request using a new mempool, and put into the request queue */
	strmapStruct *req_params = cmap_new(NULL);
	int req_id = cmap_gethighestkey(sdb->requests)+1;
	asdb_req_defaults(req_params->mempool, req_params);
	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "PutAttributes" ) );
	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval( req_params->mempool, domain, domain_sz ) );
	edb_net_item_attr_t *name_attr = strmap_lookup(affected_item, "_name", 5);
	strmap_assign( req_params, "ItemName", 8, asdb_req_mkval( req_params->mempool, name_attr->value, name_attr->value_sz ) );


	asdb_set_attrs( req_params, affected_item );
	
	simpledb_req_t *sdbr = mempool_malloc_named(req_params->mempool, sizeof(simpledb_req_t), "simpledb request context");
	if(!sdbr)
	{
		cmap_destroy( req_params );
		return -1;
	}
	sdbr->req_params = req_params;
	sdbr->sdb = sdb;
	sdbr->req_id = req_id;
	sdbr->cb = NULL;
	cmap_assign( sdb->requests, req_id, sdbr );
	return 0;
}

/* get all items from a domain */
int asdb_req_queue_getall( simpledb_t *sdb, const char *domain, size_t domain_sz )
{
	/* construct the request using a new mempool, and put into the request queue */
	strmapStruct *req_params = cmap_new(NULL);
	int req_id = cmap_gethighestkey(sdb->requests)+1;
	asdb_req_defaults(req_params->mempool, req_params);
	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "Query" ) );
	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval( req_params->mempool, domain, domain_sz ) );
	
	simpledb_req_t *sdbr = mempool_malloc_named(req_params->mempool, sizeof(simpledb_req_t), "simpledb request context");
	if(!sdbr)
	{
		cmap_destroy( req_params );
		return -1;
	}
	sdbr->req_params = req_params;
	sdbr->sdb = sdb;
	sdbr->req_id = req_id;
	sdbr->cb = asdb_get_query_items;
	cmap_assign( sdb->requests, req_id, sdbr );
	return 0;
}

/* get an item from a domain */
int asdb_req_queue_get( simpledb_t *sdb, const char *domain, size_t domain_sz, const char *item, size_t item_sz )
{
	/* construct the request using a new mempool, and put into the request queue */
	strmapStruct *req_params = cmap_new(NULL);
	int req_id = cmap_gethighestkey(sdb->requests)+1;
	asdb_req_defaults(req_params->mempool, req_params);
	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "GetAttributes" ) );
	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval( req_params->mempool, domain, domain_sz ) );
	strmap_assign( req_params, "ItemName", 8, asdb_req_mkval( req_params->mempool, item, item_sz ) );
	
	simpledb_req_t *sdbr = mempool_malloc_named(req_params->mempool, sizeof(simpledb_req_t), "simpledb request context");
	if(!sdbr)
	{
		cmap_destroy( req_params );
		return -1;
	}
	sdbr->req_params = req_params;
	sdbr->sdb = sdb;
	sdbr->req_id = req_id;
	sdbr->cb = asdb_get_item_attrs;
	cmap_assign( sdb->requests, req_id, sdbr );
	return 0;
}

/* deletes a domain */
int asdb_req_queue_domain_delete( simpledb_t *sdb, const char *domain, size_t domain_sz )
{
	/* construct the request using a new mempool, and put into the request queue */
	strmapStruct *req_params = cmap_new(NULL);
	int req_id = cmap_gethighestkey(sdb->requests)+1;
	asdb_req_defaults(req_params->mempool, req_params);
	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "DeleteDomain" ) );
	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval( req_params->mempool, domain, domain_sz ) );
	
	simpledb_req_t *sdbr = mempool_malloc_named(req_params->mempool, sizeof(simpledb_req_t), "simpledb request context");
	if(!sdbr)
	{
		cmap_destroy( req_params );
		return -1;
	}
	sdbr->req_params = req_params;
	sdbr->sdb = sdb;
	sdbr->req_id = req_id;
	sdbr->cb = NULL;
	cmap_assign( sdb->requests, req_id, sdbr );
	return 0;
}


/* creates a domain */
int asdb_req_queue_domain_create( simpledb_t *sdb, const char *domain, size_t domain_sz )
{
	/* construct the request using a new mempool, and put into the request queue */
	strmapStruct *req_params = cmap_new(NULL);
	int req_id = cmap_gethighestkey(sdb->requests)+1;
	asdb_req_defaults(req_params->mempool, req_params);
	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "CreateDomain" ) );
	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval( req_params->mempool, domain, domain_sz ) );
	
	simpledb_req_t *sdbr = mempool_malloc_named(req_params->mempool, sizeof(simpledb_req_t), "simpledb request context");
	if(!sdbr)
	{
		cmap_destroy( req_params );
		return -1;
	}
	sdbr->req_params = req_params;
	sdbr->sdb = sdb;
	sdbr->req_id = req_id;
	sdbr->cb = NULL;
	cmap_assign( sdb->requests, req_id, sdbr );
	return 0;
}



/* called to start the next request */
void asdb_req_do_next( simpledb_t *sdb )
{
	struct evhttp_request *req = NULL;	
	int next_req_id = cmap_getlowestkey(sdb->requests);
	simpledb_req_t *sdbr = cmap_lookup( sdb->requests, next_req_id );

	if(!sdbr)
	{
		/* we're done */
		return;
	}
	char *req_str = asdb_req_create( sdbr->req_params->mempool, sdb->access_info, sdbr->req_params );
	
	printf("req_str is: %s\n", req_str );

//	evcon = evhttp_connection_new("127.0.0.1", 8080);

	sdb->evcon = evhttp_connection_new("sdb.amazonaws.com", 80);
	if (sdb->evcon == NULL)
	{
		fprintf(stdout, "failed to connnect\n");
	}

	req = evhttp_request_new(asdb_http_request_done, sdbr);
	evhttp_add_header(req->output_headers, "Host", "sdb.amazonaws.com");
	/* We give ownership of the request to the connection */
	if (evhttp_make_request(sdb->evcon, req, EVHTTP_REQ_GET, req_str) == -1) {
		fprintf(stdout, "FAILED\n");

		evhttp_connection_free( sdb->evcon );
	}
}


void asdb_req_test(simpledb_t *sdb)
{

	strmapStruct *req_params = cmap_new(NULL);

	asdb_req_defaults(req_params->mempool, req_params);
/*	
	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "ListDomains" ) );
	
*/	

	strmap_assign( req_params, "Action", 6, asdb_req_mkval2( req_params->mempool, "Query" ) );

	strmap_assign( req_params, "DomainName", 10, asdb_req_mkval2( req_params->mempool, "test" ) );
//	strmap_assign( req_params, "ItemName", 8, asdb_req_mkval2( req_params->mempool, "value 2" ) );

//	strmap_assign( req_params, "hhhh", 4, asdb_req_mkval( req_params->mempool, "value 3", 7 ) );
//	strmap_assign( req_params, "qqqq", 4, asdb_req_mkval( req_params->mempool, "value 4", 7 ) );

	

	char *req_str = asdb_req_create( req_params->mempool, sdb->access_info, req_params );
	
	printf("req_str is: %s\n", req_str );
	
	
	

}






